﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace DirectorySlicer
{
    /// <summary>
    /// class to do the copying job
    /// </summary>
    class FilesBinPackerAndCopy
    {
        private List<FileContainer> files;
        private long sizeMax;
        private String cutOff;
        private String partDirPrefix;
        private String destPath;
        private Form1 form;
        private bool makeHardLinks;

        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="files">list of files</param>
        /// <param name="sourceFolderPath">source folder</param>
        /// <param name="destFolderPath">destination path</param>
        /// <param name="binSize">specified size for each bin in byte</param>
        /// <param name="form">form to invoke updates</param>
        /// <param name="makeHardLinks">true, if to use hardlinks</param>
        public FilesBinPackerAndCopy(List<FileContainer> files, String sourceFolderPath, String destFolderPath, long binSize, Form1 form, bool makeHardLinks)
        {
            this.files = files;
            this.sizeMax = binSize;
            this.destPath = destFolderPath;
            this.cutOff = sourceFolderPath;
            String[] pathParts = sourceFolderPath.Split("\\".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
            this.partDirPrefix = pathParts[pathParts.Length - 1];
            this.form = form;
            this.makeHardLinks = makeHardLinks;
        }

        /// <summary>
        /// calculate the bin configuration / partitions and do so with the real files
        /// </summary>
        public void binPackAndCopy()
        {
            List<Bin> bins = binPack();
            form.Invoke(new Logger.UpdateLogCallback(Logger.Log), bins.Count + " bins will be created with following sizes:");
            for (int i = 0; i < bins.Count; i++)
                form.Invoke(new Logger.UpdateLogCallback(Logger.Log), (i + 1) + ".) " + workerThread.convertByteToMB(bins[i].getSize()) + "MB (" + bins[i].getCount() + " files)");
            doCopy(bins);
        }

        private void doCopy(List<Bin> bins)
        {
            int part = 1;
            foreach (Bin bin in bins)
            {
                foreach (FileContainer file in bin.getFiles())
                {
                    /*
                     * the following statements calculate the new path of the file. missing and existing of leading and ending backslashes make life a little difficult...
                     */
                    String fileName = file.getLocation();
                    //delete the leading part of path, which equals the source path
                    fileName = fileName.Replace(cutOff, "");
                    String[] fileNamePathParts = fileName.Split("\\".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                    String fileNameRestPath = "";
                    if (fileNamePathParts.Length > 1)
                    {
                        //file is nested in a subfolder of the source path
                        fileNameRestPath = fileName.Replace(fileNamePathParts[fileNamePathParts.Length - 1], "");
                        if (fileNameRestPath[0] == "\\".ToCharArray()[0])
                            fileNameRestPath = fileNameRestPath.Substring(1);
                    }
                    fileName = fileNamePathParts[fileNamePathParts.Length - 1];
                    //foldername of the bin is created
                    String path = System.IO.Path.Combine(destPath, partDirPrefix + "_" + part, fileNameRestPath);
                    if (!System.IO.Directory.Exists(path))
                        System.IO.Directory.CreateDirectory(path);
                    fileName = System.IO.Path.Combine(path, fileName);
                    if (!System.IO.File.Exists(fileName))
                    {
                        if (makeHardLinks)
                            erstelleLink(file.getLocation(), fileName);
                        else
                            System.IO.File.Copy(file.getLocation(), fileName, true);
                    }
                    form.Invoke(new Logger.UpdateProgressCallback(Logger.progressAdd));
                }
                part++;
            }
        }

        private List<Bin> binPack()
        {
            List<Bin> bins = new List<Bin>();
            files.Sort();
            foreach (FileContainer file in files)
            {
                bool packed = false;
                //bins.Sort(); //to need or not to need - that is the question? advantage has to be proved...
                foreach(Bin bin in bins)
                    if (bin.getSize() + file.getSize() <= sizeMax)
                    {
                        bin.addFile(file);
                        packed = true;
                    }
                if (!packed)
                {
                    Bin bin = new Bin();
                    bin.addFile(file);
                    bins.Add(bin);
                }
            }
            return bins;
        }

        //needed to make the hard links
        [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)]
        static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr IpSecurityAttributes);
        public static void erstelleLink(String quelle, String ziel)
        {
            CreateHardLink(ziel, quelle, IntPtr.Zero);
        }
    }
}
